/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sqlcipher;

import net.sqlcipher.CharArrayBuffer;
import ohos.data.resultset.ResultSet;
import ohos.rpc.IRemoteObject;
import ohos.rpc.MessageParcel;
import ohos.utils.Parcel;
import ohos.utils.Sequenceable;

/**
 * A buffer containing multiple cursor rows.
 */
public class CursorWindow extends ohos.data.resultset.SharedBlock implements Sequenceable {
    private long nWindow;
    private int mStartPos;
    private int mRequiredPos;
    CursorWindowHelper cursorWindowHelper = new CursorWindowHelper();
    private static CursorWindowAllocation allocation = new DefaultCursorWindowAllocation();

    public static void setCursorWindowAllocation(CursorWindowAllocation value){
        allocation = value;
    }
    public static CursorWindowAllocation getCursorWindowAllocation() {
        return allocation;
    }

    /**
     * Creates a new empty window.
     *
     * @param localWindow true if this window will be used in this process only
     */
    public CursorWindow(boolean localWindow) {
        super(""); // todo no super constructor taking in boolean value
        mStartPos = 0;
        if(allocation == null){
            allocation = new DefaultCursorWindowAllocation();
        }
        cursorWindowHelper = new CursorWindowHelper();
        native_init(localWindow,
            allocation.getInitialAllocationSize(),
            allocation.getGrowthPaddingSize(),
            allocation.getMaxAllocationSize());
    }
    /**
     * Returns the starting position of this window within the entire
     * Cursor's result set.
     *
     * @return the starting position of this window within the entire
     * Cursor's result set.
     */
    public int getStartRowIndex() {
        return mStartPos;
    }

    /**
     * Set the start position of cursor window
     * @param pos
     */
    public void setStartRowIndex(int pos) {
        mStartPos = pos;
    }

    public int getRequiredPosition(){
        return mRequiredPos;
    }

    public void setRequiredPosition(int pos) {
        mRequiredPos = pos;
    }

    /**
     * Returns the number of rows in this window.
     *
     * @return the number of rows in this window.
     */
    public int getRowCount() {
        cursorWindowHelper.acquireReference();
        try {
            return getNumRows_native();
        } finally {
        cursorWindowHelper.releaseReference();
        }
    }

    private native int getNumRows_native();
    /**
     * Set number of Columns
     * @param columnNum
     * @return true if success
     */
    public boolean setColumnCount(int columnNum) {
        cursorWindowHelper.acquireReference();
        try {
            return setNumColumns_native(columnNum);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean setNumColumns_native(int columnNum);

    /**
     * Allocate a row in cursor window
     * @return false if cursor window is out of memory
     */
    public boolean allocateRow(){
        cursorWindowHelper.acquireReference();
        try {
            return allocRow_native();
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean allocRow_native();

    /**
     * Free the last row
     */
    public void freeLastRow(){
        cursorWindowHelper.acquireReference();
        try {
            freeLastRow_native();
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native void freeLastRow_native();

    /**
     * copy byte array to cursor window
     * @param value
     * @param row
     * @param col
     * @return false if fail to copy
     */
    public boolean putBlob(int row, int col, byte[] value) {
        cursorWindowHelper.acquireReference();
        try {
            return putBlob_native(value, row - mStartPos, col); // todo native methods needs to check format
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean putBlob_native(byte[] value, int row, int col);

    /**
     * Copy String to cursor window
     * @param value
     * @param row
     * @param col
     * @return false if fail to copy
     */
    public boolean putString(int row, int col, String value) {
        cursorWindowHelper.acquireReference();
        try {
            return putString_native(value, row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean putString_native(String value, int row, int col);

    /**
     * Copy integer to cursor window
     * @param value
     * @param row
     * @param col
     * @return false if fail to copy
     */
    public boolean putLong(int row, int col, long value) {
        cursorWindowHelper.acquireReference();
        try {
            return putLong_native(value, row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean putLong_native(long value, int row, int col);


    /**
     * Copy double to cursor window
     * @param value
     * @param row
     * @param col
     * @return false if fail to copy
     */
    public boolean putDouble(int row, int col, double value) {
        cursorWindowHelper.acquireReference();
        try {
            return putDouble_native(value, row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean putDouble_native(double value, int row, int col);

    /**
     * Set the [row, col] value to NULL
     * @param row
     * @param col
     * @return false if fail to copy
     */
    public boolean putNull(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return putNull_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean putNull_native(int row, int col);


    /**
     * Returns {@code true} if given field is {@code NULL}.
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return {@code true} if given field is {@code NULL}
     */
    public boolean isNull(int row, int col) {   //deprecated
        cursorWindowHelper.acquireReference();
        try {
            return isNull_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean isNull_native(int row, int col);

    /**
     * Returns a byte array for the given field.
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return a String value for the given field
     */
    public byte[] getBlob(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return getBlob_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Returns the value at (<code>row</code>, <code>col</code>) as a <code>byte</code> array.
     *
     * <p>If the value is null, then <code>null</code> is returned. If the
     * type of column <code>col</code> is a string type, then the result
     * is the array of bytes that make up the internal representation of the
     * string value. If the type of column <code>col</code> is integral or floating-point,
     * then an {@link SQLiteException} is thrown.
     *
     * @param row row
     * @param col column
     * @return byte[]
     */
    private native byte[] getBlob_native(int row, int col);

    /**
     * Returns data type of the given column's value.
     *<p>
     * Returned column types are
     * <ul>
     *   <li>{@link Cursor#FIELD_TYPE_NULL}</li>
     *   <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
     *   <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
     *   <li>{@link Cursor#FIELD_TYPE_STRING}</li>
     *   <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
     *</ul>
     *</p>
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return the value type
     */
    public ResultSet.ColumnType getType(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            int value =  getType_native(row - mStartPos, col);
            return getColumnTypefromNative(value);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    public ResultSet.ColumnType getColumnTypefromNative(int value) {
        if (value == 0) {
            return ResultSet.ColumnType.TYPE_NULL;
        } else if (value == 1) {
            return ResultSet.ColumnType.TYPE_INTEGER;
        } else if (value == 2) {
            return ResultSet.ColumnType.TYPE_FLOAT;
        } else if (value == 3) {
            return ResultSet.ColumnType.TYPE_STRING;
        } else {
            return ResultSet.ColumnType.TYPE_BLOB;
        }
    }

    /**
     * Checks if a field contains either a blob or is null.
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return {@code true} if given field is {@code NULL} or a blob
     * @deprecated use {@link #getType(int, int)} instead
     */
    public boolean isBlob(int row, int col) { //deprecated
        cursorWindowHelper.acquireReference();
        try {
            return isBlob_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Checks if a field contains a long
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return {@code true} if given field is a long
     * @deprecated use {@link #getType(int, int)} instead
     */
    public boolean isLong(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return isInteger_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Checks if a field contains a float.
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return {@code true} if given field is a float
     * @deprecated use {@link #getType(int, int)} instead
     */
    public boolean isFloat(int row, int col) { //deprecated
        cursorWindowHelper.acquireReference();
        try {
            return isFloat_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Checks if a field contains either a String or is null.
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return {@code true} if given field is {@code NULL} or a String
     * @deprecated use {@link #getType(int, int)} instead
     */
    public boolean isString(int row, int col) { //deprecated
        cursorWindowHelper.acquireReference();
        try {
            return isString_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native boolean isBlob_native(int row, int col);
    private native boolean isString_native(int row, int col);
    private native boolean isInteger_native(int row, int col);
    private native boolean isFloat_native(int row, int col);

    private native int getType_native(int row, int col);

    /**
     * Returns a String for the given field.
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return a String value for the given field
     */
    public String getString(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return getString_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Returns the value at (<code>row</code>, <code>col</code>) as a <code>String</code>.
     *
     * <p>If the value is null, then <code>null</code> is returned. If the
     * type of column <code>col</code> is integral, then the result is the string
     * that is obtained by formatting the integer value with the <code>printf</code>
     * family of functions using format specifier <code>%lld</code>. If the
     * type of column <code>col</code> is floating-point, then the result is the string
     * that is obtained by formatting the floating-point value with the
     * <code>printf</code> family of functions using format specifier <code>%g</code>.
     * If the type of column <code>col</code> is a blob type, then an
     * {@link SQLiteException} is thrown.
     *
     * @param row row
     * @param col column
     * @return string
     */
    private native String getString_native(int row, int col);
    //private native byte[] getString_native(int row, int col);

    /**
     * copy the text for the given field in the provided char array.
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @param buffer the CharArrayBuffer to copy the text into,
     * If the requested string is larger than the buffer
     * a new char buffer will be created to hold the string. and assigne to
     * CharArrayBuffer.data
     */
    public void copyStringToBuffer(int row, int col, CharArrayBuffer buffer) {
        if (buffer == null) {
            throw new IllegalArgumentException("CharArrayBuffer should not be null");
        }
        if (buffer.data == null) {
            buffer.data = new char[64];
        }
        cursorWindowHelper.acquireReference();
        try {
            char[] newbuf = copyStringToBuffer_native(
                row - mStartPos, col, buffer.data.length, buffer); //todo hos native api is required
            if (newbuf != null) {
                buffer.data = newbuf;
            }
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    private native char[] copyStringToBuffer_native(
        int row, int col, int bufferSize, CharArrayBuffer buffer);

    /**
     * Returns a long for the given field.
     * row is 0 based
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return a long value for the given field
     */
    public long getLong(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return getLong_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Returns the value at (<code>row</code>, <code>col</code>) as a <code>long</code>.
     *
     * <p>If the value is null, then <code>0L</code> is returned. If the
     * type of column <code>col</code> is a string type, then the result
     * is the <code>long</code> that is obtained by parsing the string value with
     * <code>strtoll</code>. If the type of column <code>col</code> is
     * floating-point, then the result is the floating-point value casted to a <code>long</code>.
     * If the type of column <code>col</code> is a blob type, then an
     * {@link SQLiteException} is thrown.
     *
     * @param row row
     * @param col column
     * @return value
     */
    private native long getLong_native(int row, int col);

    /**
     * Returns a double for the given field.
     * row is 0 based
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return a double value for the given field
     */
    public double getDouble(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return getDouble_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Returns the value at (<code>row</code>, <code>col</code>) as a <code>double</code>.
     *
     * <p>If the value is null, then <code>0.0</code> is returned. If the
     * type of column <code>col</code> is a string type, then the result
     * is the <code>double</code> that is obtained by parsing the string value with
     * <code>strtod</code>. If the type of column <code>col</code> is
     * integral, then the result is the integer value casted to a <code>double</code>.
     * If the type of column <code>col</code> is a blob type, then an
     * {@link SQLiteException} is thrown.
     *
     * @param row row
     * @param col column
     * @return value
     */
    private native double getDouble_native(int row, int col);

    /**
     * Returns a short for the given field.
     * row is 0 based
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return a short value for the given field
     */
    public short getShort(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return (short) getLong_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Returns an int for the given field.
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return an int value for the given field
     */
    public int getInt(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return (int) getLong_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Returns a float for the given field.
     * row is 0 based
     *
     * @param row the row to read from, row - getStartPosition() being the actual row in the window
     * @param col the column to read from
     * @return a float value for the given field
     */
    public float getFloat(int row, int col) {
        cursorWindowHelper.acquireReference();
        try {
            return (float) getDouble_native(row - mStartPos, col);
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /**
     * Clears out the existing contents of the window, making it safe to reuse
     * for new data. Note that the number of columns in the window may NOT
     * change across a call to clear().
     */
    public void clear() {
        cursorWindowHelper.acquireReference();
        try {
            mStartPos = 0;
            native_clear();
        } finally {
            cursorWindowHelper.releaseReference();
        }
    }

    /** Clears out the native side of things */
    private native void native_clear();

    /**
     * Cleans up the native resources associated with the window.
     */
    public void close() {
        cursorWindowHelper.releaseReference();
    }

    private native void close_native();

    @Override
    protected void finalize() {
        // Just in case someone forgot to call close...
        if (nWindow == 0) {
            return;
        }
        close_native();
    }

    public static final Sequenceable.Producer<CursorWindow> CREATOR
        = new Sequenceable.Producer<CursorWindow>() {
        public CursorWindow createFromParcel(Parcel source) {
            return new CursorWindow(source,0);
        }
        public CursorWindow[] newArray(int size) {
            return new CursorWindow[size];
        }
    };

    public boolean unmarshalling(Parcel p) {
        return false; //not used
    }

    public static CursorWindow createFromParcel(Parcel parcel) {
        return CREATOR.createFromParcel(parcel);
    }

    public int describeContents() {
        return 0;
    }

    public boolean marshalling(Parcel dest, int flags) {
        ((MessageParcel) dest).writeRemoteObject(native_getBinder());
        return dest.writeInt(mStartPos);
    }

    public CursorWindow(Parcel source,int foo) {
        super("");
        IRemoteObject nativeBinder = ((MessageParcel) source).readRemoteObject();
        mStartPos = source.readInt();

        native_init(nativeBinder);
    }

    public void acquireRef() {
        cursorWindowHelper.acquireReference();
    }

    public void releaseRef() {
        cursorWindowHelper.releaseReference();
    }

    /** Get the binder for the native side of the window
     * @return IRemoteObject
     */
    private native IRemoteObject native_getBinder();

    /** Does the native side initialization for an empty window
     * @param localOnly flag
     * @param initialSize initialSize
     * @param growthPaddingSize growthPaddingSize
     * @param maxSize max size
     */
    private native void native_init(boolean localOnly, long initialSize,
                                    long growthPaddingSize, long maxSize);

    /** Does the native side initialization with an existing binder from another process
     * @param nativeBinder IRemoteObject
     */
    private native void native_init(IRemoteObject nativeBinder);

    protected void onAllReferencesReleased() {
        close_native();
      //  super.onAllReferencesReleased(); //todo no such method in superclass
    }
}

